Hoisting - 從 ECMAScript 下手

讓我們從個小測驗著手吧

quiz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = 1
function test() {
console.log('1.', a)
var a = 7
console.log('2.', a)
a++
var a
inner()
console.log('4.', a)
function inner(){
console.log('3.', a)
a = 30
b = 200
}
}
test()
console.log('5.', a)
a = 70
console.log('6.', a)
console.log('7.', b)
  • answer :

    以 hoisting 試想

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    var a = 1
    function test() {
    var a
    console.log('1.', a) // '1. undefined'
    a = 7
    console.log('2.', a) // '2. 7'
    a++
    var a

    function inner() {
    console.log('3.', a) // '3. 8'
    a = 30
    b = 200
    }

    inner()
    console.log('4.', a) // '4. 30'
    }

    test()
    console.log('5.', a) // '5. 1'
    a = 70
    console.log('6.', a) // '6. 70'
    console.log('7.', b) // '7. 200'


ECMAScript

  • Execution Contexts

    When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context.

  • Variable Instantiation

    Every execution context has associated with it a variable object. Variables and functions declared in the source text are added as properties of the variable object. For function code, parameters are added as properties of the variable object.

    1
    2
    3
    4
    5
    6
    7
    VO: {
    a:1
    }

    function test() {
    var a = 1
    }

    On entering an execution context, the properties are bound to the variable object in the following order:

    • For function code: for each formal parameter, as defined in the FormalParameterList, create a property of the variable object whose name is the Identifier and whose attributes are determined by the type of code. The values of the parameters are supplied by the caller as arguments to [[Call]].

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      VO: {
      a: 123,
      b: undefined
      }

      function test(a, b) {

      }

      test(123) // 會使參數初始化

    • For each FunctionDeclaration in the code, in source text order, create a property of the variable object.

      If the variable object already has a property with this name, replace its value and attributes.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      VO: {
      a: 123,
      b: function
      }

      function test(a, b) {
      function b() {

      } // 若有同名的,則會被後面的所覆蓋
      }

      test(123)

    • For each VariableDeclaration or VariableDeclarationNoIn in the code, create a property of the variable object whose value is undefined.

      If there is already a property of the variable object with the name of a declared variable, the value of the property and its attributes are not changed.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      VO: {
      a: 123,
      b: function,
      c: undefined
      }

      function test(a, b) {
      function b() {
      }
      var c = 30
      }

      test(123)


了解原理後,回到上述的 quiz

quiz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = 1
function test(){
console.log('1.', a)
var a = 7
console.log('2.', a)
a++
var a
inner()
console.log('4.', a)
function inner(){
console.log('3.', a)
a = 30
b = 200
}
}
test()
console.log('5.', a)
a = 70
console.log('6.', a)
console.log('7.', b)
  • answer :

    體驗在 JS 引擎中此段程式碼如何被執行

  1. 產生 global Execution Context,並初始化 global VO

    1
    2
    3
    global EC
    global VO {
    }

  2. 首先尋找參數,但因為此段並不是 function 所以沒有參數。

  3. 接著尋找是否有 function 的宣告 -> 找到 test() 的宣告。

    1
    2
    3
    4
    global EC
    global VO {
    test: func
    }

  4. 尋找變數宣告

    1
    2
    3
    4
    5
    global EC
    global VO {
    test: func,
    a: undefined
    }

  5. 開始執行程式

    • Line 1 : var a = 1;

      1
      2
      3
      4
      5
      global EC
      global VO {
      test: func,
      a: 1
      }

    • Line 2~15 : function 宣告跳過

    • Line 16 : 當呼叫一個新的 function test() ,就進入一個新的 execution context


  1. 產生新的 test Execution Context ,並初始化 test VO :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    test EC
    test VO {

    }

    global EC
    global VO {
    test: func,
    a: 1
    }

  2. 首先尋找參數 -> 無

  3. 接著尋找是否有 function 的宣告 -> 找到 inner() 的宣告。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    test EC
    test VO {
    inner: func
    }

    global EC
    global VO {
    test: func,
    a: 1
    }

  4. 尋找變數宣告

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    test EC
    test VO {
    inner: func,
    a: undefined
    }

    global EC
    global VO {
    test: func,
    a: 1
    }

  5. 開始執行程式

    • Line 3 : console.log('1.', a) // undefined

    • Line 4 : var a = 7

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      test EC
      test VO {
      inner: func,
      a: 7
      }

      global EC
      global VO {
      test: func,
      a: 1
      }

    • Line 5 : console.log('2.', a) // 7

    • Line 6 : a++

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      test EC
      test VO {
      inner: func,
      a: 8
      }

      global EC
      global VO {
      test: func,
      a: 1
      }

    • Line 8 : inner()


  1. 產生新的 inner Execution Context ,並初始化 inner VO :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    inner EC
    inner VO {

    }

    test EC
    test VO {
    inner: func,
    a: 8
    }

    global EC
    global VO {
    test: func,
    a: 1
    }

  2. function inner() 中沒有傳入任何參數,裡面也沒有 function 以及變數。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    inner EC
    inner VO {

    }

    test EC
    test VO {
    inner: func,
    a: 8
    }

    global EC
    global VO {
    test: func,
    a: 1
    }

  3. 開始執行程式

    • Line 11 : console.log('3.', a) // 8

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      inner EC
      inner VO {

      }

      test EC
      test VO {
      inner: func,
      a: 8
      }

      global EC
      global VO {
      test: func,
      a: 1
      }

      因為 inner VO 裡面是空的,因此往上找至 test VO -> a: 8 。

    • Line 12 : a = 30

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      inner EC
      inner VO {

      }

      test EC
      test VO {
      inner: func,
      a: 30
      }

      global EC
      global VO {
      test: func,
      a: 1
      }

    • Line 13 : b = 200

      因為 inner VO 裡面沒有 b 的資訊,因此往上找至 test VO ,發現裡面也沒有 b 的資訊,繼續往上至 global VO 找,仍沒有 b 的資訊,最後在 global VO 建立 b 的資訊 ( 讓 b 變成全域變數 )。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      inner EC
      inner VO {

      }

      test EC
      test VO {
      inner: func,
      a: 30
      }

      global EC
      global VO {
      test: func,
      a: 1,
      b: 200
      }

  4. 執行完 inner(),將 inner EC pop 出去。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    test EC
    test VO {
    inner: func,
    a: 30
    }

    global EC
    global VO {
    test: func,
    a: 1,
    b: 200
    }

  5. 回到 function test() {} 內部

    • Line 9 : console.log('4.', a) // 30
  6. 執行完 test(),將 test EC pop 出去。

    1
    2
    3
    4
    5
    6
    global EC
    global VO {
    test: func,
    a: 1,
    b: 200
    }

  7. 回到 global EC

    • Line 17 : console.log('5.', a) // 1

    • Line 18 : a = 70

      1
      2
      3
      4
      5
      6
      global EC
      global VO {
      test: func,
      a: 70,
      b: 200
      }

    • Line 19 : console.log('6.', a) // 70

    • Line 20 : console.log('7.', a) // 200

  8. 主程式執行完畢

Reference
What's `THIS` in JavaScript? let and const (ES6)

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×